#pragma once

#include "CoreMinimal.h"
#include "SkelotComponent.h"
#include "SkeletalInstancedProxy.generated.h"

struct FSkelotAnimPlayParams;
class USkelotAnimCollection;
class USkeletalInstancedSubsystem;
class USkelotComponent;

USTRUCT(BlueprintType)
struct SKELOT_API FSkeletalInstanceKey
{
	GENERATED_BODY()

	UPROPERTY(BlueprintReadOnly, VisibleAnywhere)
	USkeletalMesh* SkeletalMesh = nullptr;

	UPROPERTY(BlueprintReadOnly, VisibleAnywhere)
	int32 NumCustomDataFloats = 0;

	UPROPERTY(BlueprintReadOnly, VisibleAnywhere)
	bool bRenderCustomDepth = true;

	void Initialize(const FSkeletalInstanceKey& Key)
	{
		SkeletalMesh = Key.SkeletalMesh;
		bRenderCustomDepth = Key.bRenderCustomDepth;
		CalculateHash();
	}
	
	friend inline uint32 GetTypeHash(const FSkeletalInstanceKey& Value)
	{
		return Value.Hash;
	}
	bool operator==(FSkeletalInstanceKey const& Other) const
	{
		return SkeletalMesh == Other.SkeletalMesh &&
			NumCustomDataFloats == Other.NumCustomDataFloats && bRenderCustomDepth == Other.bRenderCustomDepth;
	}
	FName GetFName() const{
		if(Name.IsNone())
		{
			if(SkeletalMesh)
			{
				Name = SkeletalMesh->GetFName();
			}
			else
			{
				Name = TEXT("EmptySkeleton");
			}
		}
		return Name;
	}
	void CalculateHash() const{
		Hash = GetTypeHash(bRenderCustomDepth);
		if(SkeletalMesh)
		{
			Hash = HashCombine(Hash, GetTypeHash(SkeletalMesh));
		}
	}
	mutable FName Name;
	mutable uint32 Hash = 0;
};

class FSkeletalInstancedProxyAbstract
{
public:
	FSkeletalInstancedProxyAbstract(){}
	virtual ~FSkeletalInstancedProxyAbstract() {}

public:
	void Initialize(const FSkeletalInstanceKey& Key, const UObject* WorldContext);
	USkelotComponent* GetComponent() const;
	USkelotAnimCollection* GetAnimCollection() const;
	void BeginDestroy();
	const FSkeletalInstanceKey& GetInstanceKey() const {return InstanceKey;}
	USkeletalMesh* GetSkeletalMesh() const {return InstanceKey.SkeletalMesh;}
public:
	void RemoveInstance(int32 InstanceIndex);
	int32 AddInstance(const FTransform3f& WorldTransform, float InWeight = 1);
	void SetInstanceTransform(int InstanceIndex, const FTransform3f& WorldTransform);
	void PlayAnimation(int InstanceIndex, const FSoftObjectPath& InAnimation, const FSkelotAnimPlayParams& Params);
	void PlayAnimation(int InstanceIndex, UAnimSequenceBase* InAnimation, const FSkelotAnimPlayParams& Params);
	FTransform3f GetInstanceSocketTransform(int instanceIndex, FName SocketName, bool bWorldSpace) const;
public:
	virtual void ApplyCustomData(TArray<float>& PerInstanceSMCustomData) {};
	virtual int NumCustomDataFloats() {return 0;};
	
protected:
	TWeakObjectPtr<USkelotAnimCollection> AnimCollectionWeaker = nullptr;
	TWeakObjectPtr<USkelotComponent> SkelotComponentWeaker = nullptr;
	TWeakObjectPtr<USkeletalInstancedSubsystem> SubsystemWeaker = nullptr;
	TWeakObjectPtr<UAnimSequenceBase> DefaultAnimSequence = nullptr;
	FSkeletalInstanceKey InstanceKey;
};

class FSkeletalInstancedProxy : public FSkeletalInstancedProxyAbstract
{
public:
	FSkeletalInstancedProxy();
public:
	void PlayAnimSequence(const FSoftObjectPath& InAnimation);
	void PlayAnimSequence(UAnimSequenceBase* InAnimation);
	void UpdateTransform(const FTransform3f& WorldTransform);
	FTransform3f GetSocketTransform(FName SocketName, bool bWorldSpace) const;
	void Remove();
protected:
	int32 InstanceIndex = -1;
	FSkelotAnimPlayParams AnimPlayParams;
};